package edu.pitt.cs.cs2510.project3.model;

/*
 * File: Simulator.java
 * Authors: Jesse Szwedko, Callen Shaw, Heather Friedberg
 * CS2510 Project 3
 */
import java.util.HashMap;
import java.util.Iterator;

import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;

import edu.pitt.cs.cs2510.project3.configuration.InputParameters;
import edu.pitt.cs.cs2510.project3.configuration.RequestParameters;
import edu.pitt.cs.cs2510.project3.model.replication.Replication;

public class Simulator {
	
	public enum Algorithm {
		STATIC,
		DYNAMIC,
		TEST
	}

	private InputParameters simulationParams;
	private HashMap<Integer, Client> clients;
	private HashMap<Integer, Server> servers;
	private int clock;
	private Algorithm algorithm;
	
	private Object frontEndMonitor;
	
	private final int ClockBreakpoint = 50; //allows Callen to stop the simulator at this clock time in debug mode

	public Simulator(InputParameters simulationParams, Algorithm algorithm) {
		this.simulationParams = simulationParams;
		this.algorithm = algorithm;
		this.clients = new HashMap<Integer, Client>();
		this.servers = new HashMap<Integer, Server>();
		this.frontEndMonitor = new Object();
	}

	public void run()
	{
		for (int i = 0; i < this.simulationParams.getFrontendCount(); i++)
		{
			Server server;
			
			switch (this.algorithm)
			{
				case STATIC:
					server = new StaticFrontEnd(i, simulationParams, this.frontEndMonitor);
					break;
				case DYNAMIC:
					server = new DynamicFrontEnd(i, simulationParams, this.frontEndMonitor);
					break;
				case TEST:
					server = new TestFrontEnd(i, simulationParams, this.frontEndMonitor);
					break;
				default:
					throw new IllegalArgumentException("Algorithm type not supported");
			}

			this.servers.put(i, server);
		}
		
		for(Server s : this.servers.values()){
			for(Server t : this.servers.values()){
				if(!s.equals(t)){
					((FrontEnd)s).frontEnds.put(t.id, (FrontEnd)t);
				}
			}
		}

		for (int i = 0; i < this.simulationParams.getClientCount(); i++) //construct each client
		{
			Client client = new Client(i, this.simulationParams);
			this.clients.put(i, client);
		}

		System.out.println("Servers:");
		System.out.println(this.servers);
		System.out.println("Clients:");
		System.out.println(this.clients);

		for (Server server : this.servers.values()) //start servers
		{
			server.start();
		}

		DescriptiveStatistics powerStatistics = new DescriptiveStatistics();
		while (true)
		{
			if (this.clock > this.simulationParams.getMaxRequestTime())
			{
				boolean done = true;
				for (Server server : this.servers.values())
				{
					server.noMoreRequests(true);
					if (!server.isDone())
					{
						done = false;
					}
				}
				
				for (Client client : this.clients.values())
				{
					if (!client.isDone())
					{
						done = false;
					}
				}
				

				if (done)
				{
					break;
				}
			}
			
			for (Client client : this.clients.values())
			{
				client.tick();
			}
			
			for (RequestParameters requestParameters : this.simulationParams.getRequests(this.clock)) {
				Request request = new Request(this.clients.get(requestParameters.ClientIndex), this.clock, requestParameters.FileIndex, this.simulationParams.getBlocksPerFile());
				request.server = this.clients.get(requestParameters.ClientIndex).getClosestServerWithFile(request.fileId, this.servers.values());
				request.client.addRequest(request);
				request.server.addRequest(request);
			}
			
			for (Server server : this.servers.values())
			{
				server.tick = true;
				server.time = this.clock;
			}
			
			synchronized(this.frontEndMonitor)
			{
				this.frontEndMonitor.notifyAll();
			}

			while (true)
			{
				boolean ready = true;
				for (Server server : this.servers.values())
				{
					if (server.isAlive() && !server.isReady())
					{
						server.isReady();
						ready = false;
					}
				}
				
				if (ready)
				{
					break;
				}
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					//e.printStackTrace();
					System.err.println("Simulator interrupted");
				}
			}
			
			for (Server server : this.servers.values())
			{
				powerStatistics.addValue(server.getUptimeCost());
			}

			this.clock++;
			if(this.clock == ClockBreakpoint)
				System.out.println(this.clock + " is ClockBreakpoint");
					
		}

		//wait for servers to die
		Iterator<Server> iterator= this.servers.values().iterator();
		while (iterator.hasNext())
		{
			Server server = iterator.next();
			try {
				server.die();
				synchronized(this.frontEndMonitor)
				{
					this.frontEndMonitor.notifyAll();
				}
				server.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
				System.err.println("Sever " + server.id + " was interrupted");
			}
		}
		
		System.out.println("All done " + this.clock);
		
		System.out.println("REQUESTS");
		for (Client client : this.clients.values())
		{
			for (Request request : client.completedRequests)
			{
				System.out.println(this.clients.get(request.client.id) + ", " + 
						request.fileId + ", " + request.frontEnd.id + ":" + request.server.id + ", " + request.completionTime);
			}
		}
		
		System.out.println("REPLICATIONS");
		for (Server server : this.servers.values())
		{
			for (Replication replication : server.replications)
			{
				System.out.println(replication.toString(true));
			}
		}
		
		System.out.println("SERVERS");
		for (Server server : this.servers.values())
		{
			System.out.println(server.toString());
			((FrontEnd)server).printWorkers();
		}
		
		System.out.println("TOTALS");
		
		int totalRequests = 0, totalMessages = 0;
		double effectiveDelay = 0;
		DescriptiveStatistics lagStatistics = new DescriptiveStatistics();
		
		for (Client client : this.clients.values())
		{
			totalRequests += client.completedRequests.size();
			for (Request request : client.completedRequests)
			{
				lagStatistics.addValue(request.lag);
			}
		}
		
		effectiveDelay = lagStatistics.getSum() + powerStatistics.getSum() + (totalMessages * this.simulationParams.getMessageWeight());
		
		System.out.println("Requests: " + totalRequests);
		
		System.out.println("Lag:");
		System.out.println("Total: " + lagStatistics.getSum());
		System.out.println("Min: " + lagStatistics.getMin());
		System.out.println("Max: " + lagStatistics.getMax());
		System.out.println("Average: " + lagStatistics.getMean());
		System.out.println("Standard Deviation " + lagStatistics.getStandardDeviation());
		
		System.out.println("Power:");
		System.out.println("Total: " + powerStatistics.getSum());
		System.out.println("Min: " + powerStatistics.getMin());
		System.out.println("Max: " + powerStatistics.getMax());
		System.out.println("Average: " + powerStatistics.getMean());
		System.out.println("Standard Deviation " + powerStatistics.getStandardDeviation());
		
		System.out.println("Messages: " + totalMessages);		
		
		System.out.println("Effective delay: $$$$" + effectiveDelay + "$$$$");
		return;
	}
}